//
// winter - Direct2D GUI framework.
// Copyright (C) 2009 Aleksandar Dezelin 
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#ifndef FORM_HOST_H
#define FORM_HOST_H

#pragma once
#include "application.h"
#include "event_sink.h"
#include "intrusive_ptr.h"
#include "noncopyable.h"
#include "singleton.h"
#include "winter_assert.h"
#include <algorithm>
#include <functional>
#include <hash_map>
#include <Windows.h>

namespace winter {
namespace platform {

using namespace winter;
using namespace std;
using namespace std::tr1;

template<class T> class form_host : public services::noncopyable {

  const static wchar_t kClassName[];
  const static wchar_t kDefaultTitle[];
  const static DWORD kDefaultStyle;
  const static DWORD kDefaultExStyle;

  class host_map;
  class handler_dispatcher;
  typedef form_host<T> this_type;
  typedef function<LRESULT (this_type*, WPARAM, LPARAM)> message_handler;

public:
  form_host() : services::noncopyable(), _hwnd(NULL) {
    register_window_class();
    create_window();
    setup_dispatcher();
  }

  ////////////////////////////////////////////////////////
  /// Methods
  ////////////////////////////////////////////////////////

  ///
  /// <summary>
  ///     Hides the form.
  /// </summary>
  ///
  void hide() const {
    ::ShowWindow(_hwnd, SW_HIDE);
  }

  ///
  /// <summary>
  ///     Shows the form as modaless.
  /// </summary>
  ///
  void show() const {
    ::ShowWindow(_hwnd, SW_SHOW);
  }

  ///
  /// <summary>
  ///     Shows the form as modal.
  /// </summary>
  /// <returns>
  ///     Button id that closed the form.
  /// </returns>
  int show_modal() const {
  }

  ////////////////////////////////////////////////////////
  /// Properties
  ////////////////////////////////////////////////////////

  ///
  /// <summary>
  ///     Returns the titlebar on/off state.
  /// </summary>
  /// <returns>
  ///     Titlebar on/off state.
  /// </returns>
  ///
  bool get_titlebar() const {
    return (get_window_style() & WS_CAPTION) == WS_CAPTION;
  }

  ///
  /// <summary>
  ///     Sets the titlebar on/off state.
  /// </summary>
  /// <param name="show">
  ///     Titlebar on/off state.
  /// </param>
  ///
  void set_titlebar(bool show) {
    update_style_flags(WS_CAPTION, show);
  }

  ///
  /// <summary>
  ///     Returns the system menu on/off state.
  /// </summary>
  /// <returns>
  ///     System menu on/off state.
  /// </returns>
  ///
  bool get_sysmenu_button() const {
    return (get_window_style() & WS_SYSMENU) == WS_SYSMENU;
  }

  ///
  /// <summary>
  ///     Sets the system menu on/off state.
  /// </summary>
  /// <param name="show">
  ///     System menu on/off state.
  /// </param>
  ///
  void set_sysmenu_button(bool show) {
    update_style_flags(WS_SYSMENU, show);
  }

  ///
  /// <summary>
  ///     Returns the help button on/off state.
  /// </summary>
  /// <returns>
  ///     Help button on/off state.
  /// </returns>
  ///
  bool get_help_button() const {
    return (get_window_ex_style() & WS_EX_CONTEXTHELP) == WS_EX_CONTEXTHELP;
  }

  ///
  /// <summary>
  ///     Sets the help button on/off state.
  /// </summary>
  /// <param name="show">
  ///     Help button on/off state.
  /// </param>
  ///
  void set_help_button(bool show) {
    update_ex_style_flags(WS_EX_CONTEXTHELP, show);
  }

  ///
  /// <summary>
  ///     Returns the minimize button on/off state.
  /// </summary>
  /// <returns>
  ///     Minimize button on/off state.
  /// </returns>
  ///
  bool get_minimize_button() const {
    return (get_window_style() & WS_MINIMIZEBOX) == WS_MINIMIZEBOX;
  }

  ///
  /// <summary>
  ///     Sets the minimize button on/off state.
  /// </summary>
  /// <param name="show">
  ///     Minimize button on/off state.
  /// </param>
  ///
  void set_minimize_button(bool show) {
    update_style_flags(WS_MINIMIZEBOX, show);
  }

  ///
  /// <summary>
  ///     Returns the maximize button on/off state.
  /// </summary>
  /// <returns>
  ///     Maximize button on/off state.
  /// </returns>
  ///
  bool get_maximize_button() const {
    return (get_window_style() & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX;
  }

  ///
  /// <summary>
  ///     Sets the maximize button on/off state.
  /// </summary>
  /// <param name="show">
  ///     Maximize button on/off state.
  /// </param>
  ///
  void set_maximize_button(bool show) {
    update_style_flags(WS_MAXIMIZEBOX, show);
  }

  ////////////////////////////////////////////////////////
  /// Events
  ////////////////////////////////////////////////////////

  typedef function<LRESULT (WPARAM, LPARAM)> delegate_mouse_move;
  events::event_sink<delegate_mouse_move> event_mouse_move;

  typedef function<LRESULT (WPARAM, LPARAM)> delegate_close;
  events::event_sink<delegate_close> event_close;

  typedef function<LRESULT (WPARAM, LPARAM)> delegate_paint;
  events::event_sink<delegate_paint> event_paint;

private:
  DWORD get_window_style() const {
    return static_cast<DWORD>(::GetWindowLongPtr(_hwnd, GWL_STYLE));
  }

  void set_window_style(DWORD style) {
    ::SetWindowLongPtr(_hwnd, GWL_STYLE, style);
  }

  DWORD get_window_ex_style() const {
    return static_cast<DWORD>(::GetWindowLongPtr(_hwnd, GWL_EXSTYLE));
  }

  void set_window_ex_style(DWORD style_ex) {
    ::SetWindowLongPtr(_hwnd, GWL_EXSTYLE, style_ex);
  }

  void update_style_flags(DWORD flags, bool set) {
    DWORD style = get_window_style();
    style = (set) ? style | flags : style & ~flags;
    set_window_style(style);
    update_style_change();
  }

  void update_ex_style_flags(DWORD flags, bool set) {
    DWORD style_ex = get_window_ex_style();
    style_ex = (set) ? style_ex | flags : style_ex & ~flags;
    set_window_ex_style(style_ex);
    update_style_change();
  }

  void update_style_change() {
    ::SetWindowPos(_hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | 
      SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOREPOSITION |
      SWP_NOSIZE | SWP_NOZORDER);
  }

  void create_window() {
    application* app = services::singleton<application>::instance();
    WINTER_ASSERT(app);

    _hwnd = ::CreateWindowEx(kDefaultExStyle, kClassName, L"", kDefaultStyle, 
      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, 
      app->get_instance(), NULL);
    WINTER_ASSERT(_hwnd);
  }

  void register_window_class() {
    application* app = services::singleton<application>::instance();
    WINTER_ASSERT(app);

    WNDCLASSEX wcx;
    if(::GetClassInfoEx(app->get_instance(), kClassName, &wcx))
      return;

    memset(&wcx, 0, sizeof(wcx));
    wcx.cbClsExtra = 0;
    wcx.cbSize = sizeof(wcx);
    wcx.cbWndExtra = 0;
    wcx.hbrBackground = ::GetSysColorBrush(COLOR_WINDOW + 1);
    wcx.hCursor = ::LoadCursor(NULL, IDC_ARROW);
    wcx.hIcon = ::LoadIcon(NULL, IDI_WINLOGO);
    wcx.hIconSm = NULL;
    wcx.hInstance = app->get_instance();
    wcx.lpfnWndProc = window_proc;
    wcx.lpszClassName = kClassName;
    wcx.lpszMenuName = NULL;
    wcx.style = CS_DBLCLKS;
    ::RegisterClassEx(&wcx);
  }

  void setup_dispatcher() {
    handler_dispatcher* dispatcher = 
      services::singleton<handler_dispatcher>::instance();
    WINTER_ASSERT(dispatcher);

    dispatcher->set_form_host(_hwnd, this);
    handler_dispatcher::handler_type check_handler = dispatcher->get_handler(WM_MOUSEMOVE);
    if(check_handler)
      return;

    dispatcher->set_handler(WM_MOUSEMOVE, &this_type::on_mouse_move_handler);
    dispatcher->set_handler(WM_CLOSE, &this_type::on_close);
    dispatcher->set_handler(WM_PAINT, &this_type::on_paint);
  }

  // Message handlers

  static LRESULT CALLBACK window_proc(HWND hwnd, UINT message, WPARAM wparam, 
    LPARAM lparam) {
    handler_dispatcher* dispatcher = 
      services::singleton<handler_dispatcher>::instance();
    WINTER_ASSERT(dispatcher);

    handler_dispatcher::handler_type handler = dispatcher->get_handler(message);
    if(!handler)
      return ::DefWindowProc(hwnd, message, wparam, lparam);

    handler_dispatcher::host_type* form_host = dispatcher->get_form_host(hwnd);
    if(!form_host)
      return ::DefWindowProc(hwnd, message, wparam, lparam);

    return handler(form_host, wparam, lparam);
  }

  LRESULT on_mouse_move_handler(WPARAM wparam, LPARAM lparam) {
    LRESULT result;
    auto caller = [&](delegate_mouse_move delegate_) {
      result = delegate_(wparam, lparam);
    };
    for_each(event_mouse_move.begin(), event_mouse_move.end(), caller);
    return result;
  }

  LRESULT on_close(WPARAM wparam, LPARAM lparam) {
    LRESULT result;
    auto caller = [&](delegate_close delegate_) {
      result = delegate_(wparam, lparam);
    };
    for_each(event_close.begin(), event_close.end(), caller);
    return result;
  }

  LRESULT on_paint(WPARAM wparam, LPARAM lparam) {
    LRESULT result;
    auto caller = [&](delegate_paint delegate_) {
      result = delegate_(wparam, lparam);
    };
    for_each(event_paint.begin(), event_paint.end(), caller);
    return result;
  }

  HWND _hwnd;
};

template<class T> class form_host<T>::host_map {
public:
  typedef form_host<T> host_type;

  host_type* get_form_host(HWND hwnd) const {
    host_map_type::const_iterator it = _map.find(hwnd);
    if(it == _map.end())
      return NULL;

    return (*it).second;
  }

  void set_form_host(HWND hwnd, host_type* form_host) {
    _map[hwnd] = form_host;
  }

  void remove_form_host(HWND hwnd) {
    _map.erase(hwnd);
  }

private:
  typedef stdext::hash_map<HWND, host_type*> host_map_type;
  host_map_type _map;
};

template<class T> class form_host<T>::handler_dispatcher
  : public services::intrusive_ptr_object {

public:
  typedef typename form_host<T>::message_handler handler_type;
  typedef typename host_map::host_type host_type;
  
  handler_type get_handler(UINT message) const {
    handler_map_type::const_iterator it = _map.find(message);
    if(it == _map.end())
      return handler_type();

    return (*it).second;
  }

  void set_handler(UINT message, handler_type handler) {
    _map[message] = handler;
  }

  void remove_handler(UINT message) {
    _map.erase(message);
  }

  host_type* get_form_host(HWND hwnd) const {
    return _hosts.get_form_host(hwnd);
  }

  void set_form_host(HWND hwnd, host_type* form_host) {
    _hosts.set_form_host(hwnd, form_host);
  }

  void remove_form_host(HWND hwnd) {
    _hosts.remove_form_host(hwnd);
  }

private:
  typedef host_map host_map_type;
  typedef stdext::hash_map<UINT, handler_type> handler_map_type;
  handler_map_type _map;
  host_map_type _hosts;
};

template<class T> const wchar_t form_host<T>::kClassName[] = 
  L"winter_form_class";
template<class T> const wchar_t form_host<T>::kDefaultTitle[] = 
  L"form";

template<class T> const DWORD form_host<T>::kDefaultStyle = 
  WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPED | 
  WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;

template<class T> const DWORD form_host<T>::kDefaultExStyle = 
  WS_EX_ACCEPTFILES | WS_EX_APPWINDOW | WS_EX_RIGHTSCROLLBAR | WS_EX_LEFT | 
  WS_EX_LTRREADING;

}
}

#endif // FORM_HOST_H
